﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;

namespace PolylineCompression
{
	public partial class MainGUI : Form
	{
		/// <summary>
		/// 当前是否正在绘制
		/// </summary>
		private bool drawing = false;

		/// <summary>
		/// 记录当前绘制的点
		/// </summary>
		private List<Point> points = new List<Point>();

		/// <summary>
		/// 绘制线段的笔实例
		/// </summary>
		private Pen linePen = new Pen(Color.Blue, 1.5f);

		/// <summary>
		/// 绘制点的笔刷实例
		/// </summary>
		private Brush pointBrush = new SolidBrush(Color.Red);

		/// <summary>
		/// 本窗体画布
		/// </summary>
		private Graphics graphics;

		public MainGUI()
		{
			InitializeComponent();
			algorithmBox.SelectedIndex = 0;
			graphics = showArea.CreateGraphics();
			graphics.CompositingQuality = CompositingQuality.HighQuality;
			graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
			graphics.SmoothingMode = SmoothingMode.HighQuality;
		}

		/// <summary>
		/// 获得点到一个线段的最短垂线距离
		/// </summary>
		/// <param name="point">点</param>
		/// <param name="line">指定线段</param>
		/// <returns>距离</returns>
		private double getPointToLineDistance(Point point, LineSegment line)
		{
			Vector startToPoint = new Vector(line.Start, point);
			Vector lineUnitVector = new Vector(line).GetUnitVector();
			return Math.Abs(startToPoint.CrossProduct(lineUnitVector));
		}

		/// <summary>
		/// 垂距法压缩
		/// </summary>
		private void offsetMethodCompression()
		{
			int threshold = 8; // 阈值
			int pointRemoved = 0; // 记录被压缩的点的个数			  
			for (int i = 1; i < points.Count - 1; i++)
			{ // 遍历第二个到n-1个点
			  // 计算当前点到前一个点和后一个点的连线的垂距
				LineSegment lastToNext = new LineSegment(points[i - 1], points[i + 1]);
				double distance = getPointToLineDistance(points[i], lastToNext);
				// 小于阈值删除
				if (distance <= threshold)
				{
					points.RemoveAt(i);
					i--;
					pointRemoved++;
				}
			}
			// 重绘线段
			showArea.Refresh();
			for (int i = 0; i < points.Count; i++)
			{
				drawPoint(points[i]);
				if (i > 0)
				{
					drawLine(points[i - 1], points[i]);
				}
			}
			zipResult.Text = "压缩了" + pointRemoved + "个点，压缩率：" + ((double)points.Count / (points.Count + pointRemoved) * 100) + "%";
		}

		/// <summary>
		/// 道格拉斯-普克法-启动部分
		/// </summary>
		private void douglasPuck()
		{
			List<int> result = new List<int>();
			// 先保留首尾两点
			result.Add(0);
			result.Add(points.Count - 1);
			douglasPuck(0, points.Count - 1, result);
			// 对结果集升序排序
			result.Sort();
			List<Point> resultPoints = new List<Point>();
			foreach (int i in result)
			{
				resultPoints.Add(points[i]);
			}
			// 重绘线段
			showArea.Refresh();
			for (int i = 0; i < resultPoints.Count; i++)
			{
				drawPoint(resultPoints[i]);
				if (i > 0)
				{
					drawLine(resultPoints[i - 1], resultPoints[i]);
				}
			}
			zipResult.Text = "压缩了" + (points.Count - resultPoints.Count) + "个点，压缩率：" + ((double)resultPoints.Count / points.Count * 100) + "%";
		}

		/// <summary>
		/// 道格拉斯-普克算法-递归部分
		/// </summary>
		/// <param name="startIndex">线段起始点索引</param>
		/// <param name="endIndex">线段终止点索引</param>
		/// <param name="resultPointIndexes">用于储存结果点集的索引的集</param>
		private void douglasPuck(int startIndex, int endIndex, List<int> resultPointIndexes)
		{
			int threshold = 8; // 阈值
			int maxIndex = startIndex + 1; // 遍历起始点到终止点之间的每个点的最大距离
			double maxHeight = 0;
			for (int i = startIndex + 2; i <= endIndex - 1; i++)
			{
				LineSegment startToEnd = new LineSegment(points[startIndex], points[endIndex]);
				maxHeight = getPointToLineDistance(points[maxIndex], startToEnd);
				if (getPointToLineDistance(points[i], startToEnd) > maxHeight)
				{
					maxIndex = i;
				}
			}
			// 记录最大点然后执行遍历
			if (maxHeight > threshold)
			{
				resultPointIndexes.Add(maxIndex);
				douglasPuck(startIndex, maxIndex, resultPointIndexes);
				douglasPuck(maxIndex, endIndex, resultPointIndexes);
			}
		}

		/// <summary>
		/// 在当前画布上画一个点
		/// </summary>
		/// <param name="point">传入点对象</param>
		private void drawPoint(Point point)
		{
			// 点的直径
			int sideLength = 4;
			// 绘制点中心
			Point center = new Point(point.X - sideLength / 2, point.Y - sideLength / 2);
			Rectangle rectangle = new Rectangle(center, new Size(sideLength, sideLength));
			// 绘制圆点
			graphics.FillEllipse(pointBrush, rectangle);
		}

		/// <summary>
		/// 在当前画布上画一条线
		/// </summary>
		/// <param name="start">连接起始点</param>
		/// <param name="end">连接终止点</param>
		private void drawLine(Point start, Point end)
		{
			graphics.DrawLine(linePen, start, end);
		}

		/// <summary>
		/// 开始或者结束绘画进程
		/// </summary>
		/// <param name="start">true则为开始进程</param>
		private void controlDrawing(bool start)
		{
			drawing = start;
			this.start.Enabled = !start;
			zip.Enabled = !start;
			stop.Enabled = start;
			if (start)
			{
				points.Clear();
				showArea.Refresh();
			}
		}

		private void start_Click(object sender, System.EventArgs e)
		{
			controlDrawing(true);
		}

		private void stop_Click(object sender, System.EventArgs e)
		{
			controlDrawing(false);
		}

		private void showArea_MouseClick(object sender, MouseEventArgs e)
		{
			if (drawing)
			{
				Point point = new Point(e.X, e.Y);
				drawPoint(point);
				points.Add(point);
				if (points.Count > 1)
				{
					drawLine(point, points[points.Count - 2]);
				}
			}
		}

		private void zip_Click(object sender, System.EventArgs e)
		{
			if (points.Count < 3)
			{
				MessageBox.Show("请至少画两条线段！", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
				return;
			}
			switch (algorithmBox.SelectedIndex)
			{
				case 0:
					offsetMethodCompression();
					break;
				case 1:
					douglasPuck();
					break;
				default:
					break;
			}
			zip.Enabled = false;
		}
	}
}